package com.gome.boot.config.dubbo;

import com.alibaba.dubbo.config.*;
import com.alibaba.dubbo.config.spring.ServiceBean;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

/**
 * Dubbo基本配置.
 * <br/>具体的应用继承后实现生产者和消费者.
 *
 * @author baoxiufeng
 */
public abstract class BaseDubboConfig {
    private static final String KEY_DUBBO_REGISTRY_CLIENT = "dubbo.registry.client";
    private static final String KEY_DUBBO_REGISTRY_ADDRESS = "dubbo.registry.address";
    private static final String KEY_DUBBO_REGISTRY_PROTOCOL = "dubbo.registry.protocol";
    private static final String KEY_DUBBO_REGISTRY_TIMEOUT = "dubbo.registry.timeout";
    private static final String KEY_DUBBO_APPLICATION_NAME = "dubbo.application.name";
    private static final String KEY_DUBBO_APPLICATION_LOGGER = "dubbo.application.logger";
    private static final String KEY_DUBBO_PROTOCOL_NAME = "dubbo.protocol.name";
    private static final String KEY_DUBBO_PROTOCOL_PORT = "dubbo.protocol.port";
    private static final String KEY_DUBBO_PRODUCER_GROUP = "dubbo.producer.group";
    private static final String KEY_DUBBO_PRODUCER_LOADBALANCE = "dubbo.producer.loadbalance";
    private static final String KEY_DUBBO_PRODUCER_TIMEOUT = "dubbo.producer.timeout";
    private static final String KEY_DUBBO_PRODUCER_CHATSET = "dubbo.producer.chatset";
    private static final String KEY_DUBBO_PRODUCER_THREADS = "dubbo.producer.threads";
    private static final String KEY_DUBBO_CONSUMER_LOADBALANCE = "dubbo.consumer.loadbalance";
    private static final String KEY_DUBBO_CONSUMER_TIMEOUT = "dubbo.consumer.timeout";
    private static final String KEY_DUBBO_CONSUMER_CHECK = "dubbo.consumer.check";

    @Autowired
    protected Environment env;

    /**
     * 默认基于zookeeper注入dubbo注册中心配置.
     *
     * @return dubbo注册中心配置
     */
    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress(env.getRequiredProperty(KEY_DUBBO_REGISTRY_ADDRESS));
        registryConfig.setClient(env.getProperty(KEY_DUBBO_REGISTRY_CLIENT, "curator"));
        registryConfig.setProtocol(env.getProperty(KEY_DUBBO_REGISTRY_PROTOCOL, "zookeeper"));
        registryConfig.setTimeout(env.getProperty(KEY_DUBBO_REGISTRY_TIMEOUT, Integer.class, 50000));
        return registryConfig;
    }

    /**
     * 注入dubbo上下文配置.
     * <br/>日志策略默认采用slf4j.
     *
     * @return dubbo上下文配置
     */
    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName(env.getProperty(KEY_DUBBO_APPLICATION_NAME, "com.gome.boot"));
        applicationConfig.setLogger(env.getProperty(KEY_DUBBO_APPLICATION_LOGGER, "slf4j"));
        return applicationConfig;
    }

    /**
     * 默认基于dubbo协议提供服务.
     *
     * @return 协议配置
     */
    @Bean
    public ProtocolConfig protocolConfig() {
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName(env.getProperty(KEY_DUBBO_PROTOCOL_NAME, "dubbo"));
        protocolConfig.setPort(env.getProperty(KEY_DUBBO_PROTOCOL_PORT, Integer.class, -1));
        return protocolConfig;
    }

    /**
     * 配置服务端默认设置.
     *
     * @return 服务端默认设置
     */
    @Bean
    public ProviderConfig providerConfig() {
        ProviderConfig providerConfig = new ProviderConfig();
        providerConfig.setApplication(applicationConfig());
        providerConfig.setRegistry(registryConfig());
        providerConfig.setProtocol(protocolConfig());
        providerConfig.setGroup(env.getRequiredProperty(KEY_DUBBO_PRODUCER_GROUP));
        providerConfig.setTimeout(env.getProperty(KEY_DUBBO_PRODUCER_TIMEOUT, Integer.class, 10000));
        providerConfig.setLoadbalance(env.getProperty(KEY_DUBBO_PRODUCER_LOADBALANCE, "random"));
        providerConfig.setCharset(env.getProperty(KEY_DUBBO_PRODUCER_CHATSET, "UTF-8"));
        providerConfig.setPayload(Integer.MAX_VALUE);
        providerConfig.setThreads(env.getProperty(KEY_DUBBO_PRODUCER_THREADS, Integer.class, 300));
        providerConfig.setProxy("javassist");
        providerConfig.setRetries(3);
        providerConfig.setDelay(-1);
        providerConfig.setFilter("invokeLog");
        return providerConfig;
    }

    /**
     * 配置默认消费端参数.
     *
     * @return 消费端默认参数配置
     */
    @Bean
    public ConsumerConfig consumerConfig() {
        ConsumerConfig consumerConfig = new ConsumerConfig();
        consumerConfig.setApplication(applicationConfig());
        consumerConfig.setRegistry(registryConfig());
        consumerConfig.setTimeout(env.getProperty(KEY_DUBBO_CONSUMER_TIMEOUT, Integer.class, 10000));
        consumerConfig.setLoadbalance(env.getProperty(KEY_DUBBO_CONSUMER_LOADBALANCE, "random"));
        consumerConfig.setCheck(env.getProperty(KEY_DUBBO_CONSUMER_CHECK, Boolean.class, false));
        consumerConfig.setRetries(3);
        consumerConfig.setFilter("invokeLog");
        return consumerConfig;
    }

    /**
     * 配置消息端参数.
     *
     * @param registryConfig 消费端注册中心配置
     * @return 消费端配置
     */
    private ConsumerConfig consumerConfig(RegistryConfig registryConfig) {
        ConsumerConfig consumerConfig = new ConsumerConfig();
        consumerConfig.setApplication(applicationConfig());
        consumerConfig.setRegistry(registryConfig);
        consumerConfig.setTimeout(env.getProperty(KEY_DUBBO_CONSUMER_TIMEOUT, Integer.class, 10000));
        consumerConfig.setLoadbalance(env.getProperty(KEY_DUBBO_CONSUMER_LOADBALANCE, "random"));
        consumerConfig.setCheck(env.getProperty(KEY_DUBBO_CONSUMER_CHECK, Boolean.class, false));
        consumerConfig.setRetries(3);
        consumerConfig.setFilter("invokeLog");
        return consumerConfig;
    }

    /**
     * 基于zookeeper注入dubbo注册中心自定义配置.
     *
     * @param registryZkAddress 注册中心地址或地址对应DiamondKey
     * @param isKey 是否注册中心地址参数为DiamondKey（true：是、false：不是）
     * @return dubbo注册中心自定义配置
     */
    protected RegistryConfig buildRegistryConfig(String registryZkAddress, boolean isKey) {
        if (registryZkAddress == null) {
            return null;
        }
        RegistryConfig registryConfig = new RegistryConfig();
        if (isKey) {
            registryConfig.setAddress(env.getRequiredProperty(registryZkAddress));
        } else {
            registryConfig.setAddress(registryZkAddress);
        }
        registryConfig.setClient(env.getProperty(KEY_DUBBO_REGISTRY_CLIENT, "curator"));
        registryConfig.setProtocol(env.getProperty(KEY_DUBBO_REGISTRY_PROTOCOL, "zookeeper"));
        registryConfig.setTimeout(env.getProperty(KEY_DUBBO_REGISTRY_TIMEOUT, Integer.class, 50000));
        return registryConfig;
    }

    /**
     * 构建指定类型Dubbo接口的默认消费者实例.
     *
     * @param clazz Dubbo接口Class对象
     * @param <T> 接口Class类型
     * @return 消费者实例
     */
    protected <T> T buildConsumer(Class<T> clazz) {
        return buildConsumer(null, clazz, null, false);
    }

    /**
     * 构建指定类型Dubbo接口的默认消费者实例.
     *
     * @param clazz Dubbo接口Class对象
     * @param <T> 接口Class类型
     * @param retries 失败重试次数（默认3次）
     * @return 消费者实例
     */
    protected <T> T buildConsumer(Class<T> clazz, int retries) {
        return buildConsumer(null, clazz, null, false, retries);
    }

    /**
     * 构建指定类型Dubbo接口的默认消费者实例.
     *
     * @param group Dubbo接口所属组
     * @param clazz Dubbo接口Class对象
     * @param <T> 接口Class类型
     * @return 消费者实例
     */
    protected <T> T buildConsumer(String group, Class<T> clazz) {
        return buildConsumer(group, clazz, null, false);
    }

    /**
     * 构建指定类型Dubbo接口的默认消费者实例.
     *
     * @param group Dubbo接口所属组
     * @param clazz Dubbo接口Class对象
     * @param <T> 接口Class类型
     * @param retries 失败重试次数（默认3次）
     * @return 消费者实例
     */
    protected <T> T buildConsumer(String group, Class<T> clazz, int retries) {
        return buildConsumer(group, clazz, null, false, retries);
    }

    /**
     * 构建指定类型Dubbo接口的默认消费者实例.
     *
     * @param group Dubbo接口所属组
     * @param clazz Dubbo接口Class对象
     * @param check 是否执行依赖检测
     * @param <T> 接口Class类型
     * @return 消费者实例
     */
    protected <T> T buildConsumer(String group, Class<T> clazz, boolean check) {
        return buildConsumer(group, clazz, null, check);
    }

    /**
     * 构建指定类型Dubbo接口的默认消费者实例.
     *
     * @param group Dubbo接口所属组
     * @param clazz Dubbo接口Class对象
     * @param check 是否执行依赖检测
     * @param <T> 接口Class类型
     * @param retries 失败重试次数（默认3次）
     * @return 消费者实例
     */
    protected <T> T buildConsumer(String group, Class<T> clazz, boolean check, int retries) {
        return buildConsumer(group, clazz, null, check, retries);
    }

    /**
     * 构建指定类型Dubbo接口的默认消费者实例.
     *
     * @param group Dubbo接口所属组
     * @param clazz Dubbo接口Class对象
     * @param version Dubbo接口版本
     * @param check 是否执行依赖检测
     * @param <T> 接口Class类型
     * @return 消费者实例
     */
    protected <T> T buildConsumer(String group, Class<T> clazz, String version, boolean check) {
        return buildConsumer(registryConfig(), group, clazz, version, check);
    }

    /**
     * 构建指定类型Dubbo接口的默认消费者实例.
     *
     * @param group Dubbo接口所属组
     * @param clazz Dubbo接口Class对象
     * @param version Dubbo接口版本
     * @param check 是否执行依赖检测
     * @param <T> 接口Class类型
     * @param retries 失败重试次数（默认3次）
     * @return 消费者实例
     */
    protected <T> T buildConsumer(String group, Class<T> clazz, String version, boolean check, int retries) {
        return buildConsumer(registryConfig(), group, clazz, version, check, retries);
    }

    /**
     * 构建指定类型Dubbo接口的消费者实例.
     *
     * @param registryZkAddress Dubbo接口注册地址
     * @param group Dubbo接口所属组
     * @param clazz Dubbo接口Class对象
     * @param version Dubbo接口版本
     * @param check 是否执行依赖检测
     * @param <T> 接口Class类型
     * @return 消费者实例
     */
    protected <T> T buildConsumer(String registryZkAddress, String group, Class<T> clazz, String version, boolean check) {
        return buildConsumer(buildRegistryConfig(registryZkAddress, false), group, clazz, version, check);
    }

    /**
     * 构建指定类型Dubbo接口的消费者实例.
     *
     * @param registryZkAddress Dubbo接口注册地址
     * @param isDiamondKey Dubbo接口注册地址是否通过DiamondKey获取标识
     * @param group Dubbo接口所属组
     * @param clazz Dubbo接口Class对象
     * @param version Dubbo接口版本
     * @param check 是否执行依赖检测
     * @param <T> 接口Class类型
     * @return 消费者实例
     */
    protected <T> T buildConsumer(String registryZkAddress, boolean isDiamondKey, String group, Class<T> clazz, String version, boolean check) {
        return buildConsumer(buildRegistryConfig(registryZkAddress, isDiamondKey), group, clazz, version, check);
    }

    /**
     * 构建指定类型Dubbo接口的消费者实例.
     *
     * @param registryConfig Dubbo接口注册中心配置
     * @param group Dubbo接口所属组
     * @param clazz Dubbo接口Class对象
     * @param version Dubbo接口版本
     * @param check 是否执行依赖检测
     * @param <T> 接口Class类型
     * @return 消费者实例
     */
    private <T> T buildConsumer(RegistryConfig registryConfig, String group, Class<T> clazz, String version, boolean check) {
        return buildConsumer(registryConfig, group, clazz, version, check, 3);
    }

    /**
     * 构建指定类型Dubbo接口的消费者实例.
     *
     * @param registryConfig Dubbo接口注册中心配置
     * @param group Dubbo接口所属组
     * @param clazz Dubbo接口Class对象
     * @param version Dubbo接口版本
     * @param check 是否执行依赖检测
     * @param <T> 接口Class类型
     * @param retries 失败重试次数（默认3次）
     * @return 消费者实例
     */
    private <T> T buildConsumer(RegistryConfig registryConfig, String group, Class<T> clazz, String version, boolean check, int retries) {
        ReferenceConfig<T> ref = new ReferenceConfig<>();
        ref.setGroup(group);
        ref.setVersion(version);
        ref.setInterface(clazz);
        ref.setCheck(check);
        ref.setConsumer(consumerConfig(registryConfig));
        ref.getConsumer().setRetries(retries);
        ref.setRetries(retries);
        return ref.get();
    }

    /**
     * 构建指定类型Dubbo接口的提供者实例.
     *
     * @param serviceObj 提供者实例对象
     * @param <T> 提供者类型泛型
     * @return 提供者Dubbo接口实例对象
     */
    protected <T> ServiceBean<T> buildProvider(T serviceObj) {
        return buildProvider(serviceObj, null, null);
    }

    /**
     * 构建指定类型Dubbo接口的提供者实例.
     *
     * @param serviceObj 提供者实例对象
     * @param group 分组配置
     * @param <T> 提供者类型泛型
     * @return 提供者Dubbo接口实例对象
     */
    protected <T> ServiceBean<T> buildProvider(T serviceObj, String group) {
        return buildProvider(serviceObj, group, null);
    }

    /**
     * 构建指定类型Dubbo接口的提供者实例.
     *
     * @param serviceObj 提供者实例对象
     * @param group 分组配置
     * @param version 版本配置
     * @param <T> 提供者类型泛型
     * @return 提供者Dubbo接口实例对象
     */
    protected <T> ServiceBean<T> buildProvider(T serviceObj, String group, String version) {
        ServiceBean<T> serviceBean = new ServiceBean<T>();
        serviceBean.setGroup(group);
        serviceBean.setVersion(version);
        Class clazz = serviceObj.getClass();
        if (AopUtils.isAopProxy(serviceObj)) {
            clazz = AopUtils.getTargetClass(serviceObj);
        }
        serviceBean.setInterface(clazz.getInterfaces()[0]);
        serviceBean.setRef(serviceObj);
        serviceBean.setProvider(providerConfig());
        return serviceBean;
    }
}
